/* 
 * Copyright 2009 Shared Object (www.sharedobject.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package edos.prm.artifact;

import java.util.HashSet;
import java.util.Set;

/**
 * An {@code ArtifactSet} is a parametrizable class that represents a set of
 * attributes of the same type.
 * 
 * @version 1.0
 */
public final class ArtifactSet<E extends Artifact> {

	private HashSet<E> contentSet; 
	private AttributeMatcher matcher;

	/**
	 * Create a new set of Attributes of some Artifact type.
	 */
	public ArtifactSet() {
		contentSet = new HashSet<E>();
		matcher = AttributeMatcher.getInstance();
	}

	/**
	 * Create a new set of Attributes of a given type from the provided
	 * set of values.
	 *
	 * @param set an initial set of values of type E
	 */
	public ArtifactSet(Set<E> set) {
		contentSet = new HashSet<E>();
		contentSet.addAll(set);
		matcher = AttributeMatcher.getInstance();
	}

	/**
	 * Adds an Artifact to the current set.
	 * 
	 * @param attribute The Artifact to add.
	 */
	public synchronized void add(E attribute) {
		contentSet.add(attribute);
	}

	/**
	 * Adds a set of Attributes to thisArtifactSet.
	 * 
	 * @param attributes the set of {@code Attribute}s to add.
	 */
	public synchronized void add(Set<E> attributes) {
		contentSet.addAll(attributes);
	}

	/**
	 * Remove an attribute from this attribute set.
	 * 
	 * @param attribute the attribute to remove from this set.
	 */
	public synchronized void remove(E attribute) {
		this.contentSet.remove(attribute);
	}

	/**
	 * Remove a set of attributes from this attribute set.
	 * 
	 * @param attributes a set of attributes to remove from this set.
	 */
	public synchronized void remove(Set<E> attributes) {
		contentSet.removeAll(attributes);
	}

	/**
	 * Returns the contents of this ArtifactSet as a set of Attributes.
	 * 
	 * @return an array of attributes representing the contents of this set
	 */
	public synchronized Set<E> getContents() {
		HashSet<E> result = new HashSet<E>();
		result.addAll(contentSet);
		return result;
	}
	
	/**
	 * Evaluates an {@code intersection} expression.
	 * <p>
	 * Takes two {@code ArtifactSet}s as parameter, compares contents and
	 * returns a new {@code ArtifactSet} representing the intersection of the
	 * two sets.
	 * 
	 * @param set1 an ArtifactSet being intersected
	 * @param set2 the second of the ArtifactSet argmens
	 * @return a new set that is the intersection of this and the argument set
	 */
	public static <E extends Artifact> ArtifactSet<E>
	intersection(ArtifactSet<E> set1, ArtifactSet<E> set2) {
		ArtifactSet<E> temp = new ArtifactSet<E>();
		for ( E e : set1.getContents() )
			if ( set2.getContents().contains(e))
				temp.add(e);
		return temp;
	}

	/**
	 * Evaluates an {@code union} expression.
	 * <p>
	 * Takes an {@code ArtifactSet set} as a parameter, compares its content
	 * to the content of a second set and returns a new {@code ArtifactSet}
	 * representing the union of the two sets.
	 * 
	 * @param set1 an ArtifactSet being unioned
	 * @param set2 the second of the attribute sets
	 * @return a new set that is the union of this and the argument set
	 */
	public static <E extends Artifact> ArtifactSet<E>
	unionWith(ArtifactSet<E> set1, ArtifactSet<E> set2) {
		ArtifactSet<E> temp = new ArtifactSet<E>();
		temp.add(set1.getContents());
		temp.add(set2.getContents());
		return temp;
	}

	/**
	 * Evaluates a {@code set difference} expression.
	 * <p>
	 * Takes two {@code ArtifactSet}s as parameter, compares its content
	 * to the content of this object and returns a new {@code ArtifactSet}
	 * representing the first argument set with the second argument set's
	 * contents removed.
	 * 
	 * @param set1 an ArtifactSet
	 * @param set2 a second ArtifactSet
	 * @return a new set that represents set1 set minus the argument set
	 */
	public static <E extends Artifact> ArtifactSet<E>
	minus(ArtifactSet<E> set1, ArtifactSet<E> set2) {
		ArtifactSet<E> temp = new ArtifactSet<E>();
		for(E elem : set1.contentSet)
			if ( !set2.contains(elem) )
				temp.add(elem);
		return temp;
	}

	/**
	 * Indicates if this set contains a specific element
	 * 
	 * @param elem an artifact of type E
	 * @return true if this set contains elem
	 */
	public synchronized boolean contains(E elem) {
		return this.contains(elem);
	}

	/**
	 * Match an artifact to the current set.
	 * <p>
	 * An {@code Artifact} matches an {@code ArtifactSet} if that artifact
	 * matches one of the elements of this set.
	 * 
	 * @param artifact an artifact object being matched with this set
	 * @return true if the Artifact set matches provided Artifact.
	 */
	public boolean matches(E artifact) {
		for (E attr: contentSet) {
			if (matcher.match(artifact, attr))
				return true;
		}
		// No match
		return false;
	}

	/**
	 * Match an artifact set to the current set.
	 * <p>
	 * An ArtifactSet A matches an ArtifactSet B if for every element x of
	 * set A, there is an element y of set B such that x matchs y.
	 * <p>
	 * In this method, A is the argument set and B is this object set.
	 * 
	 * @param set an {@code ArtifactSet} being compared with this object
	 * @return true if the argument set matches this set.
	 */
	public boolean matches(ArtifactSet<E> set) {
		for ( E elem : set.getContents() )
			if ( !this.matches(elem ) )
				return false;
		return true;
	}	

	/**
	 * Returns the size of the ArtifactSet, i.e. its cardinality.
	 * @return the cardinality of  the ArtifactSet
	 */
	public int size() {
		return contentSet.size();
	}
	
	/**
	 * Indicates if this set is currently empty
	 * @return true if this set is empty.
	 */
	public boolean isEmpty() {
		return this.contentSet.isEmpty();
	}
}
